; Updated 11/15/2024 - Create Module
#Requires AutoHotkey v2.0
#SingleInstance force

#Include base.ahk

SendMode("Event")
; SendMode("Input")
SetWorkingDir(A_ScriptDir)

X := XUtils()

; ################ MODULE ################
; Class                 WIA_ImageFile
; Description           Use the Windows API "WIA.ImageFile" to obtain image information
;   WIA.ImageFile Requirements: Windows Vista+, Windows Server 2003 R2+
;   https://learn.microsoft.com/zh-cn/previous-versions/windows/desktop/wiaaut/-wiaaut-imagefile
; Args
;   filepath            Image file path
class WIA_ImageFile {
    __new(filepath) {
        this.raw := ComObject("WIA.ImageFile")
        if !this.raw {
            throw MemoryError("the WIA.ImageFile module could not be loaded")
        }
        this.reload(filepath)
    }

    /**
     * 重新加载图片
     * @param {String} filepath 图片文件的路径
     */
    reload(filepath) {
        ret := this.raw.LoadFile(filepath)
        this.width := this.raw.Width
        this.height := this.raw.Height
        this.active_frame := this.raw.ActiveFrame
        this.argb_data := this.raw.ARGBData ; Vector
        this.file_data := this.raw.FileData ; Vector
        this.file_extension := this.raw.FileExtension
        this.pixel_depth := this.raw.PixelDepth
        return ret
    }
}
; image := WIA_ImageFile("debug_image.png")
; XPrint("image size: {}x{}", image.width, image.height)

; ################ MODULE ################
class XCoord {
    ; Description           创建一个坐标实例
    ; Args
    ;   x                   X 轴坐标
    ;   y                   Y 轴坐标
    __new(x := 0, y := 0) {
        this.x := x
        this.y := y
    }

    ; Description           当实例被调用的时候可以对坐标进行重新赋值
    ; Args
    ;   x                   X 轴坐标
    ;   y                   Y 轴坐标
    call(x := 0, y := 0) {
        this.x := x
        this.y := y
        return this
    }

    /**
     * 检测当前坐标是否有效
     * @param {Boolean} is_strict 严格模式，当为 true 时出现坐标无效的情况会报错，反之只是返回当前坐标是否有效的 Boolean 值
     * @returns {Integer} 
     */
    isValid(is_strict := false) {
        if (this.x >= 0 or this.y >= 0) {
            return true
        }
        if (is_strict) {
            Throw ValueError(Format("x: {}, y: {} are invalid coordinates", this.x, this.y), -1)
        }
        return false
    }

    click(opts := "") {
        coordMode("Mouse")
        click(this.x, this.y, opts)
        return this
    }

    clickL(is_down := unset, count := 1) {
        opts := count " Left "
        if (IsSet(is_down)) {
            if (is_down) {
                opts := opts "Down "
            } else {
                opts := opts "Up "
            }
        }
        this.click("Left " opts)
        return this
    }

    clickR(is_down := unset, count := 1) {
        opts := count "Right "
        if (isSet(is_down)) {
            if (is_down) {
                opts := opts "Down "
            } else {
                opts := opts "Up "
            }
        }
        this.click(opts)
        return this
    }

    wheelU(count := 1) {
        opts := count " WheelUp"
        this.click(opts)
        return this
    }

    wheelD(count := 1) {
        opts := count " WheelDown"
        this.click(opts)
        return this
    }
}

; ################ MODULE ################
class XImage {
    __new(filepath) {
        this.raw := WIA_ImageFile(filepath)
        this.filepath := filepath
    }

    ; If no found, use CoordMode("Pixel") to try changing the coord mode
    ; Default match is 0.875 accuracy
    find(opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        if (subStr(opts, -1) != " ") {
            opts := opts " "
        }
        found_x := -1
        found_y := -1
        coordMode("Pixel")
        if imageSearch(&found_x, &found_y, x1, y1, x2, y2, opts this.filepath) {
            return XCoord(found_x + this.raw.width / 2, found_y + this.raw.height / 2)
        }
        return XCoord(-1, -1)
    }

    waitShowUp(wait_time := 3000, opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        start_time := X.now()
        coord := this.isExists(opts, x1, y1, x2, y2)
        if coord.isValid() {
            return coord
        }
        while (X.now() - wait_time <= start_time) {
            sleep(61.8)
            coord := this.isExists(opts, x1, y1, x2, y2)
            if coord.isValid() {
                return coord
            }
        }
        return coord
    }

    waitVanish(wait_time := 3000, opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        start_time := X.now()
        coord := this.isExists(opts, x1, y1, x2, y2)
        if (!coord.isValid()) {
            return coord
        }
        while (X.now() - wait_time <= start_time) {
            sleep(61.8)
            coord := this.isExists(opts, x1, y1, x2, y2)
            if (!coord.isValid()) {
                return coord
            }
        }
        return coord
    }

    isExists(opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        coord := this.find(opts, x1, y1, x2, y2)
        if (coord.x >= 0 and coord.y >= 0) {
            return coord
        }
        return XCoord(-1, -1)
    }
}

; ################ MODULE ################
; 搜索方法很捞，搜不出来
class XPixel {
    __new(frist_rgb, offset_rgb) {
        this.frist_rgb := frist_rgb
        color_list := strSplit(offset_rgb, ",")
        this.offset_rgb := []
        ; array_x := []
        ; array_y := []
        for index, color in color_list {
            t_color := strSplit(color, "|")
            if (X.len(t_color) != 3) {
                throw ValueError(x.format("颜色 {} 格式错误，格式应为 x_offset|y_offset|rgb_color", color))
            }
            t_color[1] := number(t_color[1])
            t_color[2] := number(t_color[2])
            ; t_color[3] := X.fromBase(t_color[3])

            ; array_x.push(t_color[1])
            ; array_y.push(t_color[2])

            this.offset_rgb.push(t_color)
        }
        ; this.centre_offser_x := (max(array_x) - min(array_x)) // 2
        ; this.centre_offser_y := (max(array_y) - min(array_y)) // 2
    }

    ; If no found, use CoordMode("Pixel") to try changing the coord mode
    ; Default match is 0.9375 accuracy
    ; 默认搜索方式为从左往右，搜索从 X1 和 Y1 指定的坐标开始，并检查 X1 到 X2 这一行的所有像素是否匹配；
    ; 如果从 X1 到 X2 那里没有找到匹配的像素, 则继续向 Y2 逐行搜索, 直到找到匹配的像素
    ; 参考链接：https://autohotkey.top/v2/docs/commands/PixelSearch.htm
    find(variation := 2, x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        found_x := -1
        found_y := -1
        coordMode("Pixel")
        if !pixelSearch(&found_x, &found_y, x1, y1, x2, y2, this.frist_rgb, variation) {
            return XCoord(-1, -1)
        }
        X.print(10003, found_x, found_y)
        is_match := true
        for index, color in this.offset_rgb {
            t_fx := -1
            t_fy := -1
            if pixelGetColor(found_x + color[1], found_y + color[2]) != "0x" color[3] {
                is_match := false
                break
            }
        }
        if is_match {
            return XCoord(found_x, found_y)
        }
        ; 找不到匹配的多点，继续完成当前行的 X 轴扫描
        X.print(10001, found_x + 1, found_y, x2, found_y)
        coord := this.find(variation, found_x + 1, found_y, x2, found_y)
        if coord.isValid() {
            X.print(10004)
            return coord
        }
        ; 找不到匹配的多点，继续完成剩下的屏幕扫描
        if y1 != y2 {
            X.print(10002, x1, found_y + 1, x2, y2)
            coord := this.find(variation, x1, found_y + 1, x2, y2)
            if coord.isValid() {
                X.print(10005)
                return coord
            }
        }
        ; while (true) {
        ;     if pixelSearch(&found_x, &found_y, rd_x_sta, rd_y_sta, x2, y2, this.frist_rgb, variation) {
        ;         is_match := true
        ;         for index, color in this.offset_rgb {
        ;             if !pixelSearch(, , found_x - 1, found_y - 1, found_x + 1, found_y + 1, this.color, variation) {
        ;                 is_match := false
        ;                 break
        ;             }
        ;         }
        ;         if is_match {
        ;             return XCoord(found_x, found_y)
        ;         }
        ;     }
        ; }
        return XCoord(-1, -1)
    }

    waitShowUp(wait_time := 3000, opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        start_time := X.now()
        coord := this.isExists(opts, x1, y1, x2, y2)
        if (coord) {
            return coord
        }
        while (X.now() - wait_time <= start_time) {
            sleep(61.8)
            coord := this.isExists(opts, x1, y1, x2, y2)
            if (coord) {
                return coord
            }
        }
        return false
    }

    waitVanish(wait_time := 3000, opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        start_time := X.now()
        coord := this.isExists(opts, x1, y1, x2, y2)
        if (!coord) {
            return coord
        }
        while (X.now() - wait_time <= start_time) {
            sleep(61.8)
            coord := this.isExists(opts, x1, y1, x2, y2)
            if (!coord) {
                return coord
            }
        }
        return false
    }

    isExists(opts := "*32 ", x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
        coord := this.find(opts, x1, y1, x2, y2)
        if (coord.x >= 0 and coord.y >= 0) {
            return coord
        }
        return false
    }
}